#include <vector>
#include <cmath>
#include <iostream>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/foreach.hpp>
#include <iomanip>
#include "distance.h"
#include "TrajSet.h"
#include "linmatrix.h"

namespace Clustering{

	// a single point is made up of vector of doubles
	typedef boost::numeric::ublas::vector<long double> Point;
	
	//typedef std::vector<Point> Points;
	typedef LinList<Point> Points;

	typedef unsigned int ClusterId;
	typedef unsigned int PointId;	

	// a cluster is a vector of pointid
	//typedef std::vector<PointId> ClusterPoints;
	typedef LinList<PointId> ClusterPoints;

	// a set of Neighbors is a vector of pointid
	//typedef std::vector<PointId> Neighbors;
	typedef LinList<PointId> Neighbors;

	class Cluster
	{
	public:
	
		Cluster (ClusterId idClust) : clustId(idClust) {};

		inline ClusterId getClusterId(){return clustId;};
		inline ClusterPoints getCluster(){return clustPoints;};
		inline ClusterPoints * getClusterRef(){return &clustPoints;};
		inline void setClusterPoints(ClusterPoints cp){clustPoints = cp;};

		inline long double getAverageLat(){return averageLat;};
		inline void setAverageLat(long double _averageLat){averageLat = _averageLat;};

		inline long double getAverageLong(){return averageLong;};
		inline void setAverageLong(long double _averageLong){averageLong = _averageLong;};

		inline long double getAverageTimeStamp(){return averageTimeStamp;};
		inline void setAverageTimeStamp(long double _averageTimeStamp){averageTimeStamp = _averageTimeStamp;};


		
		

		void addPointToCluster(PointId p){
			//clustPoints.push_back(p);
			PointId *pToInsert = new PointId(p);
			clustPoints.insert(pToInsert);
		};


	protected:
		ClusterPoints clustPoints;
		ClusterId clustId;
		//cluster coordinates
		long double averageLat;
		long double averageLong;
		long double averageTimeStamp;

	};




	void randomInit	(Points & ps, unsigned int dims = 5, 
						unsigned int num_points = 10);

	//get the set of datas from our objects
	void getSetOfDatas(Points & ps, std::vector<TrajSet> data);

	class Clusters
	{
	public:
		Clusters (Points & ps) : _ps(ps) 
		{
			int size = _ps.get_num();
			_pointId_to_clusterId.resize(size, -1);
		};

		// assign each point to a new cluster
		void uniformPartition();

		// compute similarity - not used anymore, use too much memory
		/*
		template <typename Distance_type>
		void computeSimilarity(Distance_type & d, double thresholdGeo, double thresholdTime)
		{
			
			unsigned int size = _ps.get_num();
			

			cout << "compute1" << endl;
			double defaultSim = -1;
			_sim.resize(size, size, defaultSim);
			_simTime.resize(size, size, defaultSim);
			cout << "compute2" << endl;

			for (unsigned int i=0; i < size; i++)
			{
				
				for (unsigned int j=i+1; j < size; j++)
				{
					
					double simGeo = d.similarity(*_ps.get(i), *_ps.get(j), thresholdGeo);
					_sim.set(j, i, &simGeo);
					_sim.set(i, j, &simGeo);


					double simTime = d.similarityTime(*_ps.get(i), *_ps.get(j), thresholdTime);
					_simTime.set(j, i, &simTime);
					_simTime.set(i, j, &simTime);
	
				}

				
				
			}

			
		};*/
	
		//
		// findNeighbors(PointId pid, double threshold)
		//
		// this can be implemented with reduced complexity by using R+trees
		//
		
		void createClusterCordinates();

		Neighbors* findNeighbors(Metrics::Distance<Metrics::MeterDistance<Clustering::Point>> & d, PointId pid, long double threshold, long double thresholdGeo, long double thresholdTime);
		
		//inline Points& getPs() { return _ps;}

	protected:
		// the collection of points we are working on
		Points& _ps;
		
		// mapping point_id -> clusterId
		//std::vector<ClusterId> _pointId_to_clusterId;
		LinList<ClusterId> _pointId_to_clusterId;

		// the collection of clusters
		//std::vector<Cluster> _clusters;
		LinList<Cluster> _clusters;

		// simarity_matrix for geographic position
		//boost::numeric::ublas::matrix<double> _sim;
		//LinMatrix<double> _sim;
		// simarity_matrix for timestamp
		//boost::numeric::ublas::matrix<double> _simTime;
		//LinMatrix<double> _simTime;

		friend	
			std::ostream& operator << (std::ostream& o, const Clusters& c);
	};

}